/*
 * $Id: PrefEditWindow.java,v 1.14 2002/12/07 18:02:18 jppietsch Exp $
 * Copyright (C) 2002 Jan-Peter Pietsch
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package de.jppietsch.prefedit;

import de.jppietsch.error.Log;
import de.jppietsch.gui.MainWindow;
import de.jppietsch.gui.MultiEditor;
import de.jppietsch.gui.MultiRenderer;
import de.jppietsch.gui.RscAction;
import de.jppietsch.util.RscBundle;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;

/**
 * The Preferences Editor window contains a tree view to the preferences nodes
 * on the left and a table with the selected node's entries on the right.
 *
 * @author Jan-Peter Pietsch
 * @version $Revision: 1.14 $
 */
public class PrefEditWindow extends MainWindow {

    // fields

    /**
     * This window's preferences tree model.
     */
    private final DefaultTreeModel treeModel;

    /**
     * A table view to the currently selected preference node's keys and values.
     */
    private final JTable table;

    /**
     * A tree view to the preferences.
     */
    private final JTree tree;

    /**
     * A table model for the currently selected preference node.
     */
    private final PreferencesTableModel tableModel = new PreferencesTableModel();

    /**
     * An action to import a preferences node from an XML file.
     */
    private RscAction importAction;

    /**
     * An action to export a preferences node to an XML file.
     */
    private RscAction exportAction;

    /**
     * An action to create a new key and add it to the current node.
     */
    private RscAction newKeyAction = new RscAction(Rsc.class, Rsc.NEW_KEY) {
        public void actionPerformed(ActionEvent anEvent) {
            newKey();
        }
    };

    /**
     * An action to cut the currently selected preferences node or preference
     * and put it into the clipboard.
     */
    private RscAction cutAction = new RscAction(Rsc.class, Rsc.CUT) {
        public void actionPerformed(ActionEvent anEvent) {
            cut();
        }
    };

    /**
     * An action to copy the currently selected preferences node or preference
     * to the clipboard.
     */
    private RscAction copyAction = new RscAction(Rsc.class, Rsc.COPY) {
        public void actionPerformed(ActionEvent anEvent) {
            copy();
        }
    };

    /**
     * An action to paste the contents of the clipboard to currently selected
     * preferences node.
     */
    private RscAction pasteAction = new RscAction(Rsc.class, Rsc.PASTE) {
        public void actionPerformed(ActionEvent anEvent) {
            paste();
        }
    };

    /**
     * An action to create a new node and add it to the current node.
     */
    private RscAction newNodeAction = new RscAction(Rsc.class, Rsc.NEW_NODE) {
        public void actionPerformed(ActionEvent anEvent) {
            newNode();
        }
    };

    /**
     * Flag: "table and not tree had the focus most recently"
     */
    private boolean recentTableFocus;

    // constructors

    /**
     * Constructs this window and fills it with the GUI components.
     *
     * @param jframe
     * @throws BackingStoreException If something goes wrong.
     */
    public PrefEditWindow(JFrame jframe) throws BackingStoreException {
        super(jframe, Rsc.class);
        RootTreeNode node = new RootTreeNode();
        JMenu editMenu = addMenu(Rsc.class, Rsc.EDIT);

        initAction(editMenu, cutAction);
        initAction(editMenu, copyAction);
        editMenu.addSeparator();
        initAction(editMenu, newKeyAction);
        initAction(editMenu, newNodeAction);
        editMenu.addSeparator();
        initAction(editMenu, pasteAction);

        treeModel = new DefaultTreeModel(node);
        tree = new JTree(treeModel);
        table = new JTable(tableModel);
        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                new JScrollPane(tree), new JScrollPane(table));


        table.getColumnModel().getColumn(1).setCellRenderer(new MultiRenderer());
        table.getColumnModel().getColumn(1).setCellEditor(new MultiEditor());


        getContentPane().add(split);
        pack();
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent anEvent) {
                select(anEvent);
            }
        });
        tree.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent anEvent) {
                treeFocusGained();
            }
        });
        tree.setSelectionRow(0);

        table.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent anEvent) {
                tableFocusGained();
            }
        });
        tree.setSelectionRow(0);
        table.getSelectionModel().addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent anEvent) {
                        select(anEvent);
                    }
                });
    }

    // overwritten methods

    /**
     * Adds some additional actions to the file menu.
     *
     * @param aBundle Description of Parameter
     * @return A new menu.
     */
    protected JMenu createFileMenu(RscBundle aBundle) {
        JMenu menu = super.createFileMenu(aBundle);

        importAction = new RscAction(aBundle, Rsc.IMPORT) {
            public void actionPerformed(ActionEvent anEvent) {
                importPreferences();
            }
        };
        exportAction = new RscAction(aBundle, Rsc.EXPORT) {
            public void actionPerformed(ActionEvent anEvent) {
                exportPreferences();
            }
        };

        menu.insert(importAction, 0);
        menu.insert(exportAction, 1);
        menu.insertSeparator(2);

        return menu;
    }

    // static methods

    /**
     * Adds a new key to currently selected node.
     */
    void newKey() {
        String key = JOptionPane.showInputDialog(this, Rsc.getBundle()
                .getString(Rsc.NEW_KEY_MESSAGE));

        if (key != null) {
            selectedPrefs().put(key, "");
        }
    }

    /**
     * Cuts the selected preferences node or preference to the clipboard.
     */
    void cut() {
        try {
            if (recentTableFocus) {
                // cut single preference
                new Preference(selectedPrefs(), selectedKey()).cut();
            } else {
                PreferencesTreeNode node = selectedNode();
                // cut preferences subtree
                new Subtree(node.getPreferences()).cut();
                treeModel.removeNodeFromParent(node);
            }
        } catch (BackingStoreException exception) {
            Log.caught(exception);
        }
    }

    /**
     * Copies the selected preferences node or preference to the clipboard.
     */
    void copy() {
        Preferences prefs = selectedPrefs();

        try {
            if (recentTableFocus) {
                // cut single preference
                new Preference(prefs, selectedKey()).copy();
            } else {
                // cut preferences subtree
                new Subtree(prefs).copy();
            }
        } catch (BackingStoreException exception) {
            Log.caught(exception);
        }
    }

    /**
     * Pastes the contents of the clipboard to the selected preferences node.
     * That works for key-value pairs and for complete preference subtrees.
     */
    void paste() {
        try {
            AbstractTransferable.paste(selectedPrefs());
        } catch (IOException exception) {
            Log.caught(exception);
        } catch (UnsupportedFlavorException exception) {
            Log.caught(exception);
        }
    }

    /**
     * Adds a new subnode to currently selected node.
     */
    void newNode() {
        String key = JOptionPane.showInputDialog(this, Rsc.getBundle()
                .getString(Rsc.NEW_NODE_MESSAGE));

        if (key != null) {
            Preferences newPreferences = selectedPrefs().node(key);
            treeModel.insertNodeInto(new PreferencesTreeNode(newPreferences,
                    "<error>"), selectedNode(), selectedNode().getChildCount());
        }
    }

    /**
     * Called when the tree selection changes. Displays the right table model
     * and enables or disables some actions.
     *
     * @param anEvent contains the new tree selection.
     */
    void select(TreeSelectionEvent anEvent) {
        Object node = anEvent.getPath().getLastPathComponent();

        tableModel.setPreferences(node instanceof PreferencesTreeNode
                ? ((PreferencesTreeNode) node).getPreferences()
                : null);
        updateSelection();
    }

    /**
     * Called when the table selection changes. Enables or disables some
     * actions.
     *
     * @param anEvent contains the new table selection.
     */
    void select(ListSelectionEvent anEvent) {
        if (!anEvent.getValueIsAdjusting()) {
            updateSelection();
        }
    }

    /**
     * Called when the tree receives the keyboard focus. Disables the "cut key"
     * and "copy key" actions and, if there is a node selected, enables the "cut
     * node" and "copy node" actions.
     */
    void treeFocusGained() {
        recentTableFocus = false;
        updateSelection();
    }

    /**
     * Called when the table receives the keyboard focus. Disables the "cut
     * node" and "copy node" actions and, if there is a row selected, enables
     * the "cut key" and "copy key" actions.
     */
    void tableFocusGained() {
        recentTableFocus = true;
        updateSelection();
    }

    /**
     * Imports preferences from an XML file to the selected preferences node.
     */
    void importPreferences() {
        JFileChooser chooser = new JFileChooser();
        int option = chooser.showOpenDialog(this);

        if (option == JFileChooser.APPROVE_OPTION) {
            File file = chooser.getSelectedFile();
            try {
                FileInputStream stream = new FileInputStream(file);
                try {
                    Preferences.importPreferences(stream);
                } catch (InvalidPreferencesFormatException exception) {
                    Log.caught(exception);
                } finally {
                    stream.close();
                }
            } catch (IOException exception) {
                Log.caught(exception);
            }
        }
    }

    /**
     * Exports preferences from the selected preferences node to an XML file.
     */
    void exportPreferences() {
        JFileChooser chooser = new JFileChooser();
        int option = chooser.showSaveDialog(this);

        if (option == JFileChooser.APPROVE_OPTION) {
            File file = chooser.getSelectedFile();
            try {
                FileOutputStream stream = new FileOutputStream(file);
                try {
                    selectedPrefs().exportSubtree(stream);
                } catch (BackingStoreException exception) {
                    Log.caught(exception);
                } finally {
                    stream.close();
                }
            } catch (IOException exception) {
                Log.caught(exception);
            }
        }
    }

    // private methods

    /**
     * Updates the selection of some actions of which the state depends on tree
     * node or table row selections.
     */
    private void updateSelection() {
        boolean keySelected = table.getSelectedRow() >= 0;
        boolean nodeSelected = tree.getLastSelectedPathComponent() instanceof PreferencesTreeNode;

        newKeyAction.setEnabled(nodeSelected);
        newNodeAction.setEnabled(nodeSelected);
        exportAction.setEnabled(nodeSelected);
        pasteAction.setEnabled(nodeSelected);
        cutAction.setEnabled(keySelected || nodeSelected);
        copyAction.setEnabled(keySelected || nodeSelected);
    }

    /**
     * Description of the Method
     *
     * @return Description of the Returned Value
     */
    private PreferencesTreeNode selectedNode() {
        Object node = tree.getLastSelectedPathComponent();

        return node instanceof PreferencesTreeNode
                ? (PreferencesTreeNode) node
                : null;
    }

    /**
     * Returns the currently selected preferences node.
     *
     * @return Currently selected Preferences.
     */
    private Preferences selectedPrefs() {
        PreferencesTreeNode node = selectedNode();

        return node != null ? node.getPreferences() : null;
    }

    /**
     * Returns the currently selected preference key.
     *
     * @return Currently selected key.
     * @throws BackingStoreException If something goes wrong.
     */
    private String selectedKey() throws BackingStoreException {
        int row = table.getSelectedRow();

        return selectedPrefs().keys()[row];
    }

    /**
     * Adds the given action to the menu and the toolbar and disables it
     * initially.
     *
     * @param aMenu    to which the action is added.
     * @param anAction is added to menu and toolbar and disabled.
     */
    private void initAction(JMenu aMenu, RscAction anAction) {
        anAction.setEnabled(false);
        aMenu.add(anAction);
    }

}
